package com.suduku.calc;

import com.suduku.entity.Box;
import com.suduku.util.SudoUtil;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 数对法（一个区域内，两个数字只能在两个单元格内，则形成数对，其他值不能填入） <br/>
 *
 * 测试数据：DataConstant.XING_02_23
 */
public class SuDuiCalc extends AbstractCalc {

    @Override
    Box solve() {
        // 判断宫内数对
        Box box = twoNumBox(getGMap());
        if(box != null) {
            return box;
        }
        // 判断行内数对
        box = twoNumBox(getXMap());
        if(box != null) {
            return box;
        }
        // 判断列内数对
        box = twoNumBox(getYMap());
        return box;
    }

    /**
     * 功能描述: 查找区域内的数对 <br/>
     *
     * @param areaMap 区域集合
     * @return "com.suduku.entity.Box"
     */
    private Box twoNumBox(Map<Integer, List<Box>> areaMap) {
        // 遍历所有区域
        Set<Map.Entry<Integer, List<Box>>> entries = areaMap.entrySet();
        for (Map.Entry<Integer, List<Box>> entry : entries) {
            List<Box> list = entry.getValue();
            // 查找区域内候选字是两个数字，并且候选数字相同，且只有两个单元格的情况，形成数对
            List<Box> nmList = SudoUtil.findSuDuiBoxList(list);
            if(nmList != null && nmList.size() == 2 && SudoUtil.isEqTwoList(nmList.get(0).getCList(), nmList.get(1).getCList())) {
                // 形成数对，则可以排除区域其他宫内的情况
                // 获取数对的下标
                List<Integer> suDuiIndexs = nmList.stream().map(Box::getI).collect(Collectors.toList());
                List<Box> clearBoxList = list.stream().filter(b -> b.isBlank() && !suDuiIndexs.contains(b.getI()) && (b.getCList().contains(nmList.get(0).getCList().get(0)) || b.getCList().contains(nmList.get(0).getCList().get(1)))).collect(Collectors.toList());
                for (Box box : clearBoxList) {
                    // 是空白格，不是数对单元格，包含数对数字
                    change(box, nmList, nmList.get(0).getCList().get(0), nmList.get(0).getCList().get(1));
                    box.removeCList(nmList.get(0).getCList());
                    return box;
                }
            }
            // 遍历 1 ~ 9 个数字，在候选值中是否只有两个空白格
            for(Integer n : Box.INIT_LIST) {
                List<Box> nList = list.stream()
                        .filter(b -> b.isBlank() && b.getCList().contains(n))
                        .collect(Collectors.toList());
                // 如果在该区域内，只有两个单元格
                if(nList.size() == 2) {
                    // 遍历下一个数字，并且数字大于第一个数字（减少次数）
                    for(Integer m : Box.INIT_LIST) {
                        if(m > n) {
                            List<Box> mList = list.stream().filter(b -> b.isBlank() && b.getCList().contains(m)).collect(Collectors.toList());
                            // 如果也是只有两个单元格，并且与第一个数字的两个单元格相等。则形成数对
                            if(mList.size() == 2 && SudoUtil.isEqBoxList(nList, mList)) {
                                // 形成数对，开始剔除数据
                                List<Integer> suDuiList = SudoUtil.getList(n, m);
                                // 1、剔除本身两个单元格
                                for(Box box : nList) {
                                    if(box.getCList().size() > 2) {
                                        change(box, nList, n, m);
                                        box.setCList(suDuiList);
                                        return box;
                                    }
                                }
                                // 2、剔除区域内的其他单元格
                                // 获取数对的下标
                                List<Integer> suDuiIndexs = nList.stream().map(Box::getI).collect(Collectors.toList());
                                // 遍历当前区域
                                for (Box box : list) {
                                    // 是空白格，不是数对单元格，包含数对数字
                                    if(box.isBlank() && !suDuiIndexs.contains(box.getI()) && (box.getCList().contains(n) || box.getCList().contains(m))) {
                                        change(box, nList, n, m);
                                        box.removeCList(suDuiList);
                                        return box;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

}
